﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Cosoware.Web.Navigation
{
    /// <summary>
    /// Web-control that provides a flat navigation over a web-site map-path.
    /// </summary>
    [ToolboxData("<{0}:FlatMenu runat=server></{0}:FlatMenu>")]
    public class FlatMenu : WebControl, INamingContainer
    {
        /// <summary>
        /// The current site map node.
        /// </summary>
        private SiteMapNode _current;

        /// <summary>
        /// The current site map provider.
        /// </summary>
        private SiteMapProvider _currentProvider;

        /// <summary>
        /// The root node from current node.
        /// </summary>
        private SiteMapNode _root;

        /// <summary>
        /// Menu item style.
        /// </summary>
        private Style _menuItemStyle;

        /// <summary>
        /// Current menu item style.
        /// </summary>
        private Style _currentMenuItemStyle;

        /// <summary>
        /// Separator style.
        /// </summary>
        private Style _separatorStyle;

        /// <summary>
        /// Gets or sets a value indicating whether render root node.
        /// </summary>
        /// <value><c>true</c> if render root node; otherwise, <c>false</c>.</value>
        [DefaultValue(true)]
        public bool RenderRootNode { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether render root child nodes.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if render root child nodes; otherwise, <c>false</c>.
        /// </value>
        [DefaultValue(false)]
        public bool RenderRootChildNodes { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether render current provider only.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if render current provider only; otherwise, <c>false</c>.
        /// </value>
        [DefaultValue(false)]
        public bool RenderCurrentProviderOnly { get; set; }

        /// <summary>
        /// Gets or sets the menu item style.
        /// </summary>
        /// <value>The menu item style.</value>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content), DefaultValue((string)null), NotifyParentProperty(true), PersistenceMode(PersistenceMode.InnerProperty)]
        public Style MenuItemStyle
        {
            get
            {
                if (this._menuItemStyle == null)
                {
                    this._menuItemStyle = new Style();
                    if (base.IsTrackingViewState)
                    {
                        ((IStateManager)this._menuItemStyle).TrackViewState();
                    }
                }
                return this._menuItemStyle;
            }
        }

        /// <summary>
        /// Gets or sets current menu item style.
        /// </summary>
        /// <value>Current menu item style.</value>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content), DefaultValue((string)null), NotifyParentProperty(true), PersistenceMode(PersistenceMode.InnerProperty)]
        public Style CurrentMenuItemStyle
        {
            get
            {
                if (this._currentMenuItemStyle == null)
                {
                    this._currentMenuItemStyle = new Style();
                    if (base.IsTrackingViewState)
                    {
                        ((IStateManager)this._currentMenuItemStyle).TrackViewState();
                    }
                }
                return this._currentMenuItemStyle;
            }
        }

        /// <summary>
        /// Gets or sets current menu item style.
        /// </summary>
        /// <value>Current menu item style.</value>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content), DefaultValue((string)null), NotifyParentProperty(true), PersistenceMode(PersistenceMode.InnerProperty)]
        public Style SeparatorStyle
        {
            get
            {
                if (this._separatorStyle == null)
                {
                    this._separatorStyle = new Style();
                    if (base.IsTrackingViewState)
                    {
                        ((IStateManager)this._separatorStyle).TrackViewState();
                    }
                }
                return this._separatorStyle;
            }
        }

        /// <summary>
        /// Raises the <see cref="E:System.Web.UI.Control.Load"/> event.
        /// </summary>
        /// <param name="e">The <see cref="T:System.EventArgs"/> object that contains the event data.</param>
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            _current = SiteMap.CurrentNode;
            if (_current != null)
            {
                _currentProvider = _current.Provider;
                _root = RenderCurrentProviderOnly ? _currentProvider.RootNode : _current.RootNode;
            }
        }

        /// <summary>
        /// Renders the contents.
        /// </summary>
        /// <param name="output">The output.</param>
        protected override void RenderContents(HtmlTextWriter output)
        {
            if (_current != null)
                RenderNodesToRoot(output, _current);
        }

        /// <summary>
        /// Renders the nodes from current to root.
        /// </summary>
        /// <param name="output">The output.</param>
        /// <param name="node">The node.</param>
        private void RenderNodesToRoot(HtmlTextWriter output, SiteMapNode node)
        {
            if (node.ParentNode != null)
            {
                if(!RenderCurrentProviderOnly || node.ParentNode.Provider == _currentProvider)
                    RenderNodesToRoot(output, node.ParentNode);
            }
            if (node == _current)
                RenderCurrentNode(output, node);
            else
                RenderGeneralNode(output, node);
        }

        /// <summary>
        /// Renders the current node.
        /// </summary>
        /// <param name="output">The output.</param>
        /// <param name="node">The node.</param>
        private void RenderGeneralNode(HtmlTextWriter output, SiteMapNode node)
        {
            if (node == _root && !RenderRootNode)
            {
                return;
            }
            else
            {
                RenderSingleNode(output, node);
                RenderSeparator(output);
            }
        }

        /// <summary>
        /// Renders the current node.
        /// </summary>
        /// <param name="output">The output.</param>
        /// <param name="node">The node.</param>
        private void RenderCurrentNode(HtmlTextWriter output, SiteMapNode node)
        {
            if (node == _root && !RenderRootNode)
            {
                if(RenderRootChildNodes)
                    RenderChildNodes(output, node);
            }
            else if (node.HasChildNodes)
            {
                RenderSingleNode(output, node);
                RenderSeparator(output);
                RenderChildNodes(output, node);
            }
            else
            {
                RenderChildNodes(output, node.ParentNode);
            }
        }

        /// <summary>
        /// Renders the child nodes.
        /// </summary>
        /// <param name="output">The output.</param>
        /// <param name="node">The site map node.</param>
        private void RenderChildNodes(HtmlTextWriter output, SiteMapNode node)
        {
            for (var i = 0; i < node.ChildNodes.Count; i++)
                RenderSingleNode(output, node.ChildNodes[i]);
        }

        /// <summary>
        /// Renders the single node.
        /// </summary>
        /// <param name="output">The output.</param>
        /// <param name="node">The node.</param>
        private void RenderSingleNode(HtmlTextWriter output, SiteMapNode node)
        {
            if(node == _current)
                CurrentMenuItemStyle.AddAttributesToRender(output);
            else
                MenuItemStyle.AddAttributesToRender(output);
            output.RenderBeginTag(HtmlTextWriterTag.Span);
            output.AddAttribute(HtmlTextWriterAttribute.Href, GetEncodedUrl(node.Url));
            output.RenderBeginTag(HtmlTextWriterTag.A);
            output.Write(node.Title);
            output.RenderEndTag();
            output.RenderEndTag();
            output.WriteLine();
        }

        /// <summary>
        /// Renders the separator.
        /// </summary>
        /// <param name="output">The output.</param>
        private void RenderSeparator(HtmlTextWriter output)
        {
            SeparatorStyle.AddAttributesToRender(output);
            output.RenderBeginTag(HtmlTextWriterTag.Span);
            output.Write("&raquo;");
            output.RenderEndTag();
            output.WriteLine();
        }

        /// <summary>
        /// Gets the encoded URL.
        /// </summary>
        /// <param name="url">The URL.</param>
        /// <returns></returns>
        private string GetEncodedUrl(string url)
        {
            return HttpUtility.HtmlAttributeEncode(Page.ResolveUrl(url));
        }
    }
}
